package com.epoch.platform.common.service;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileExistsException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.epoch.base.constant.Constant;
import com.epoch.platform.common.dao.SysAttachmentFiles;
import com.epoch.platform.common.util.CommonAttachConstant;
import com.epoch.platform.common.util.CommonAttachUtils;
import com.epoch.platform.common.vo.AttachInfoDto;
import com.epoch.platform.common.vo.SaveAttachDto;
import com.jfinal.aop.Before;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Page;
import com.jfinal.plugin.activerecord.SqlPara;
import com.jfinal.plugin.activerecord.tx.Tx;

public class CommonAttachService {
    private static final transient Logger LOG = LoggerFactory.getLogger(CommonAttachService.class);
    private static final String SQL_KEY_FINDBYBUSINESSIDANDTYPE = "sysCommon.findAttachByBusinessIdAndType";
    private static final String SQL_KEY_COUNTBYSERVERPATH = "sysCommon.countAttachByServerPath";

    /**
     * 根据附件ID(主键)获取附件对象
     * @param attachmentFileId 附件ID
     * @return 附件对象
     */
    public SysAttachmentFiles getByPk(String attachmentFileId) {
        SysAttachmentFiles result = SysAttachmentFiles.dao.findById(attachmentFileId);
        return result;
    }

    /**
     * 根据指定业务单据ID查询附件对象
     * @param businessId 业务单据ID
     * @return 附件对象列表
     */
    public List<SysAttachmentFiles> findByBusinessId(String businessId) {
        return this.findByBusinessIdAndType(businessId, null);
    }

    /**
     * 根据指定业务单据ID与业务单据类型查询附件对象
     * @param businessId 业务单据ID
     * @param businessType 业务单据类型
     * @return 附件对象列表
     */
    public List<SysAttachmentFiles> findByBusinessIdAndType(String businessId, String businessType) {
        Map<String, Object> params = new HashMap<String, Object>(2);
        params.put(CommonAttachConstant.PARAM_BUSINESSID, businessId);
        params.put(CommonAttachConstant.PARAM_BUSINESSTYPE, businessType);
        List<SysAttachmentFiles> result = SysAttachmentFiles.dao.find(Db.getSqlPara(CommonAttachService.SQL_KEY_FINDBYBUSINESSIDANDTYPE, params));
        return result;
    }

    /**
     * 根据指定业务单据ID分页查询附件对象
     * @param businessId 业务单据ID
     * @param pageNum 当前页码
     * @param pageSize 页大小
     * @return 附件对象列表
     */
    public List<SysAttachmentFiles> pageByBusinessId(String businessId, int pageNum, int pageSize) {
        return this.pageByBusinessIdAndType(businessId, null, pageNum, pageSize);
    }

    /**
     * 根据指定业务单据ID与业务单据类型分页查询附件对象
     * @param businessId 业务单据ID
     * @param businessType 业务单据类型
     * @param pageNum 当前页码
     * @param pageSize 页大小
     * @return 附件对象列表
     */
    public List<SysAttachmentFiles> pageByBusinessIdAndType(String businessId, String businessType, int pageNum, int pageSize) {
        Map<String, Object> params = new HashMap<String, Object>(2);
        params.put(CommonAttachConstant.PARAM_BUSINESSID, businessId);
        params.put(CommonAttachConstant.PARAM_BUSINESSTYPE, businessType);
        Page<SysAttachmentFiles> page = SysAttachmentFiles.dao.paginate(pageNum, pageSize, Db.getSqlPara(CommonAttachService.SQL_KEY_FINDBYBUSINESSIDANDTYPE, params));
        List<SysAttachmentFiles> result = null;
        if (page != null) {
            result = page.getList();
        }
        return result;
    }

    /**
     * 保存指定业务单据ID的附件数据
     * @param businessId 业务单据ID
     * @param attachJson 附件数据JSON字符串，通常由页面&lt;bs:commonAttach&gt;组件提交
     * @return 附件对象列表
     * @throws IOException 当操作附件文件发生IO异常时抛出此异常
     */
    @Before(Tx.class)
    public List<SysAttachmentFiles> saveAttachments(String businessId, String attachJson) throws IOException {
        return this.saveAttachments(businessId, null, attachJson);
    }

    /**
     * 保存指定业务单据ID与业务单据类型的附件数据
     * @param businessId 业务单据ID
     * @param businessType 业务单据类型
     * @param attachJson 附件数据JSON字符串，通常由页面&lt;bs:commonAttach&gt;组件提交
     * @return 附件对象列表
     * @throws IOException 当操作附件文件发生IO异常时抛出此异常
     */
    @Before(Tx.class)
    public List<SysAttachmentFiles> saveAttachments(String businessId, String businessType, String attachJson) throws IOException {
        return this.saveAttachments(businessId, businessType, attachJson, false);
    }

    /**
     * 保存指定业务单据ID与业务单据类型的附件数据
     * @param businessId 业务单据ID
     * @param businessType 业务单据类型
     * @param attachJson 附件数据JSON字符串，通常由页面&lt;bs:commonAttach&gt;组件提交
     * @param detachOldBusinessData 是否将旧附件文件与先前的business数据解除关联的标志，仅用于businessId或businessType发生变化时
     * @return 附件对象列表
     * @throws IOException 当操作附件文件发生IO异常时抛出此异常
     */
    @Before(Tx.class)
    public List<SysAttachmentFiles> saveAttachments(String businessId, String businessType, String attachJson, boolean detachOldBusinessData) throws IOException {
        List<SysAttachmentFiles> resultList = null;
        if (StringUtils.isNoneEmpty(businessId, attachJson)) {
            SaveAttachDto dto = CommonAttachUtils.parseAttachJson(attachJson);
            List<AttachInfoDto> newFiles = dto.getNewFiles();
            List<AttachInfoDto> oldFiles = dto.getOldFiles();
            List<AttachInfoDto> delFiles = dto.getDelFiles();
            List<SysAttachmentFiles> toBeDelFileAttachList = new ArrayList<SysAttachmentFiles>();
            resultList = new ArrayList<SysAttachmentFiles>();
            if (CollectionUtils.isNotEmpty(delFiles)) {
                this.saveDelAttach(toBeDelFileAttachList, businessId, businessType, detachOldBusinessData, delFiles);
            }
            if (CollectionUtils.isNotEmpty(oldFiles)) {
                this.saveOldAttach(resultList, toBeDelFileAttachList, businessId, businessType, detachOldBusinessData, oldFiles);
            }
            if (CollectionUtils.isNotEmpty(newFiles)) {
                this.saveNewAttach(resultList, businessId, businessType, newFiles);
            }
            for (SysAttachmentFiles toBeDelFileAttach : toBeDelFileAttachList) {
                this.deleteAttachFileObj(toBeDelFileAttach);
            }
        }
        return resultList;
    }

    /**
     * 根据指定业务单据ID删除附件对象
     * @param businessId 业务单据ID
     */
    @Before(Tx.class)
    public void removeByBusinessId(String businessId) {
        this.removeByBusinessIdAndType(businessId, null);
    }

    /**
     * 根据指定业务单据ID与业务单据类型删除附件对象
     * @param businessId 业务单据ID
     * @param businessType 业务单据类型
     */
    @Before(Tx.class)
    public void removeByBusinessIdAndType(String businessId, String businessType) {
        List<SysAttachmentFiles> attachList = this.findByBusinessIdAndType(businessId, businessType);
        if (CollectionUtils.isNotEmpty(attachList)) {
            for (SysAttachmentFiles attach : attachList) {
                if (!attach.delete()) {
                    throw new RuntimeException("failed to delete attachment file data(" + attach.toJson() + ").");
                }
            }
            for (SysAttachmentFiles attach : attachList) {
                this.deleteAttachFileObj(attach);
            }
        }
    }

    private void deleteAttachFileObj(SysAttachmentFiles attach) {
        // 检查附件文件是否被表数据引用
        Map<String, Object> params = new HashMap<String, Object>(1);
        params.put(CommonAttachConstant.PARAM_SERVER_PATH, attach.getStr(SysAttachmentFiles.SERVER_UPLOAD_PATH));
        SqlPara sqlPara = Db.getSqlPara(CommonAttachService.SQL_KEY_COUNTBYSERVERPATH, params);
        Long cnt = Db.queryLong(sqlPara.getSql(), sqlPara.getPara());
        if (cnt == 0) {
            // 若附件文件没有被任何表数据引用，则删除文件
            String filePath = CommonAttachUtils.obtainAttachFilePath(attach);
            File file = new File(filePath);
            if (file.exists()) {
                boolean success = file.delete();
                if (!success && file.exists()) {
                    throw new RuntimeException("failed to delete attachment file '" + file.getPath() + "'.");
                }
            }
        }
    }

    private void saveDelAttach(List<SysAttachmentFiles> toBeDelFileAttachList, String businessId, String businessType, boolean detachOldBusinessData, List<AttachInfoDto> delFiles) throws IOException {
        for (AttachInfoDto attachInfo : delFiles) {
            String fileKey = attachInfo.getFileKey();
            SysAttachmentFiles attach = this.getByPk(fileKey);
            if (attach != null) {
                String oldBusinessId = attach.getStr(SysAttachmentFiles.BUSINESS_ID);
                String oldBusinessType = attach.getStr(SysAttachmentFiles.BUSINESS_TYPE);
                if (StringUtils.equals(businessId, oldBusinessId) && StringUtils.equals(businessType, oldBusinessType) || detachOldBusinessData) {
                    // 若businessId与businessType未发生变化，或者指定了将旧附件文件与先前的business数据解除关联，则删除附件数据
                    boolean success = attach.delete();
                    if (success) {
                        toBeDelFileAttachList.add(attach);
                    } else {
                        throw new RuntimeException("failed to delete attachment file data(" + attach.toJson() + ").");
                    }
                }
            }
        }
    }

    private void saveOldAttach(List<SysAttachmentFiles> resultList, List<SysAttachmentFiles> toBeDelFileAttachList, String businessId, String businessType, boolean detachOldBusinessData,
        List<AttachInfoDto> oldFiles) {
        for (AttachInfoDto attachInfo : oldFiles) {
            String fileKey = attachInfo.getFileKey();
            SysAttachmentFiles attach = this.getByPk(fileKey);
            if (attach != null) {
                attach.set(SysAttachmentFiles.DESCRIPTION, attachInfo.getDescn());
                String oldBusinessId = attach.getStr(SysAttachmentFiles.BUSINESS_ID);
                String oldBusinessType = attach.getStr(SysAttachmentFiles.BUSINESS_TYPE);
                if (StringUtils.equals(businessId, oldBusinessId) && StringUtils.equals(businessType, oldBusinessType)) {
                    // 若businessId与businessType未发生变化，则更新旧附件数据
                    boolean success = attach.update();
                    if (success) {
                        resultList.add(attach);
                    } else {
                        throw new RuntimeException("failed to update attachment file data(" + attach.toJson() + ").");
                    }
                } else {
                    // 若businessId与businessType未发生变化，则创建新附件数据并将附件文件关联到新business数据
                    SysAttachmentFiles newAttach = new SysAttachmentFiles();
                    attach.copyProperties(newAttach);
                    newAttach.remove(SysAttachmentFiles.ATTACHMENT_FILE_ID, SysAttachmentFiles.CREATE_BY, SysAttachmentFiles.CREATE_DATE, SysAttachmentFiles.CREATE_DEPT,
                        SysAttachmentFiles.UPDATE_BY, SysAttachmentFiles.UPDATE_DATE);
                    newAttach.set(SysAttachmentFiles.BUSINESS_ID, businessId);
                    if (StringUtils.isNotEmpty(businessType)) {
                        newAttach.set(SysAttachmentFiles.BUSINESS_TYPE, businessType);
                    }
                    boolean success = newAttach.save();
                    if (success) {
                        resultList.add(this.getByPk(newAttach.getStr(SysAttachmentFiles.ATTACHMENT_FILE_ID)));
                    } else {
                        throw new RuntimeException("failed to insert attachment file data(" + newAttach.toJson() + ").");
                    }
                    if (detachOldBusinessData) {
                        // 若指定了将旧附件文件与先前的business数据解除关联，则删除旧附件数据
                        success = attach.delete();
                        if (success) {
                            toBeDelFileAttachList.add(attach);
                        } else {
                            throw new RuntimeException("failed to delete attachment file data(" + attach.toJson() + ").");
                        }
                    }
                }
            }
        }
    }

    private void saveNewAttach(List<SysAttachmentFiles> resultList, String businessId, String businessType, List<AttachInfoDto> newFiles) throws IOException {
        for (AttachInfoDto attachInfo : newFiles) {
            String fileKey = attachInfo.getFileKey();
            String fileOriginName = attachInfo.getName();
            String descn = attachInfo.getDescn();
            String filePath = CommonAttachUtils.obtainUploadFilePath(fileKey);
            File file = new File(filePath);
            if (file.exists()) {
                SysAttachmentFiles attach = new SysAttachmentFiles();
                attach.set(SysAttachmentFiles.BUSINESS_ID, businessId);
                attach.set(SysAttachmentFiles.BUSINESS_TYPE, businessType);
                attach.set(SysAttachmentFiles.FILE_SIZE, file.length());
                attach.set(SysAttachmentFiles.FILE_ORIGINAL_NAME, fileOriginName);
                attach.set(SysAttachmentFiles.LOCAL_UPLOAD_PATH, StringUtils.replace(fileOriginName, "\\", "/"));
                attach.set(SysAttachmentFiles.DESCRIPTION, descn);
                String attachBasePath = CommonAttachUtils.getAttachBasePath();
                File arcFile = null;
                boolean arcFileExist = false;
                do {
                    String fileServerPath = this.genFileServerPath(attach, businessId, businessType, file);
                    arcFile = new File(attachBasePath + fileServerPath);
                    try {
                        FileUtils.moveFile(file, arcFile);
                        arcFileExist = false;
                    } catch (FileExistsException e) {
                        arcFileExist = true;
                        CommonAttachService.LOG.warn("attach file '" + arcFile.getPath() + "' already exist.", e);
                    }
                } while (arcFileExist);
                boolean success = attach.save();
                if (success) {
                    resultList.add(this.getByPk(attach.getStr(SysAttachmentFiles.ATTACHMENT_FILE_ID)));
                } else {
                    FileUtils.deleteQuietly(arcFile);
                    throw new RuntimeException("failed to insert attachment file data(" + attach.toJson() + ").");
                }
            } else {
                throw new RuntimeException("failed to get new upload attachment file '" + filePath + "'.");
            }
        }
    }

    private String genFileServerPath(SysAttachmentFiles attach, String businessId, String businessType, File file) {
        StringBuilder buff = new StringBuilder();
        buff.append(File.separatorChar);
        buff.append(businessId);
        if (StringUtils.isNotEmpty(businessType)) {
            buff.append(File.separatorChar);
            buff.append(businessType);
        }
        buff.append(File.separatorChar);
        String fileName = file.getName();
        int lastDotIdx = fileName.lastIndexOf(Constant.DOT);
        String fileNamePre = fileName;
        String fileNameSuf = "";
        if (lastDotIdx >= 0) {
            fileNamePre = fileName.substring(0, lastDotIdx);
            fileNameSuf = fileName.substring(lastDotIdx + 1);
        }
        StringBuilder nameBuff = new StringBuilder(fileNamePre);
        nameBuff.append(Constant.UNDERSCORE);
        nameBuff.append(System.currentTimeMillis());
        nameBuff.append(Constant.DOT);
        nameBuff.append(fileNameSuf);
        String newFileName = nameBuff.toString();
        buff.append(newFileName);
        String fileServerPath = StringUtils.replace(buff.toString(), "\\", "/");
        attach.set(SysAttachmentFiles.FILE_NEW_NAME, newFileName);
        attach.set(SysAttachmentFiles.FILE_EXT_NAME, fileNameSuf);
        attach.set(SysAttachmentFiles.SERVER_UPLOAD_PATH, fileServerPath);
        return fileServerPath;
    }
}
